# -*- coding:utf-8 -*-
"""
HTTP server that implements the Python WSGI protocol (PEP 333, rev 1.21).

Based on wsgiref.simple_server which is part of the standard library since 2.5.

This is a simple server for use in testing or debugging Django apps. It hasn't
been reviewed for security issues. DON'T USE IT FOR PRODUCTION USE!
"""

from __future__ import unicode_literals

from io import BytesIO
import socket
import sys
import traceback
from wsgiref import simple_server
from wsgiref.util import FileWrapper   # for backwards compatibility

from django.core.management.color import color_style
from django.core.wsgi import get_wsgi_application
from django.utils.module_loading import import_by_path
from django.utils import six
from django.utils.six.moves.urllib.parse import urljoin
from django.utils.six.moves import socketserver

__all__ = ('WSGIServer', 'WSGIRequestHandler', 'MAX_SOCKET_CHUNK_SIZE')


# If data is too large, socket will choke, so write chunks no larger than 32MB
# at a time. The rationale behind the 32MB can be found on Django's Trac:
# https://code.djangoproject.com/ticket/5596#comment:4
MAX_SOCKET_CHUNK_SIZE = 32 * 1024 * 1024  # 32 MB

"""
    该接口同样是在两处被调用，
    django/core/management/commands/runserver.py: 应该是django开发环境中command启动执行的模式
    django/core/servers/fastcgi.py: 被runfastcgi中调用，应该生成环境中的调用入口，具体的部署方式，再详细了解
"""
def get_internal_wsgi_application():                                        # 获取wsgi app应用
    """
    Loads and returns the WSGI application as configured by the user in
    ``settings.WSGI_APPLICATION``. With the default ``startproject`` layout,
    this will be the ``application`` object in ``projectname/wsgi.py``.

    This function, and the ``WSGI_APPLICATION`` setting itself, are only useful
    for Django's internal servers (runserver, runfcgi); external WSGI servers
    should just be configured to point to the correct application object
    directly.

    If settings.WSGI_APPLICATION is not set (is ``None``), we just return
    whatever ``django.core.wsgi.get_wsgi_application`` returns.

    """
    # 从settings文件中取出wsgi的配置值，其中值为：WSGI_APPLICATION = 'mysite.wsgi.application'
    from django.conf import settings
    app_path = getattr(settings, 'WSGI_APPLICATION')
    if app_path is None:
        return get_wsgi_application()               # 内部得到WSGIHandler的对象实例

    # 最终调用的接口为：get_wsgi_application
    return import_by_path(
        app_path,
        error_prefix="WSGI application '%s' could not be loaded; " % app_path
    )


class ServerHandler(simple_server.ServerHandler, object):
    error_status = str("500 INTERNAL SERVER ERROR")

    def write(self, data):
        """'write()' callable as specified by PEP 3333"""

        assert isinstance(data, bytes), "write() argument must be bytestring"

        if not self.status:
            raise AssertionError("write() before start_response()")

        elif not self.headers_sent:
            # Before the first output, send the stored headers
            self.bytes_sent = len(data)    # make sure we know content-length
            self.send_headers()
        else:
            self.bytes_sent += len(data)

        # XXX check Content-Length and truncate if too many bytes written?
        data = BytesIO(data)
        for chunk in iter(lambda: data.read(MAX_SOCKET_CHUNK_SIZE), b''):
            self._write(chunk)
            self._flush()

    def error_output(self, environ, start_response):
        super(ServerHandler, self).error_output(environ, start_response)
        return ['\n'.join(traceback.format_exception(*sys.exc_info()))]

    # Backport of http://hg.python.org/cpython/rev/d5af1b235dab. See #16241.
    # This can be removed when support for Python <= 2.7.3 is deprecated.
    def finish_response(self):
        try:
            if not self.result_is_file() or not self.sendfile():
                for data in self.result:
                    self.write(data)
                self.finish_content()
        finally:
            self.close()


"""
    http的基础server，其中这里完成WSGI协议的实现
"""
class WSGIServer(simple_server.WSGIServer, object):
    """BaseHTTPServer that implements the Python WSGI protocol"""

    request_queue_size = 10                                 # 请求队列的长度为10

    def __init__(self, *args, **kwargs):
        if kwargs.pop('ipv6', False):
            self.address_family = socket.AF_INET6
        super(WSGIServer, self).__init__(*args, **kwargs)

    def server_bind(self):
        """Override server_bind to store the server name."""
        super(WSGIServer, self).server_bind()
        self.setup_environ()


class WSGIRequestHandler(simple_server.WSGIRequestHandler, object):

    def __init__(self, *args, **kwargs):
        from django.conf import settings
        self.admin_static_prefix = urljoin(settings.STATIC_URL, 'admin/')
        # We set self.path to avoid crashes in log_message() on unsupported
        # requests (like "OPTIONS").
        self.path = ''
        self.style = color_style()
        super(WSGIRequestHandler, self).__init__(*args, **kwargs)

    def address_string(self):
        # Short-circuit parent method to not call socket.getfqdn
        return self.client_address[0]

    def log_message(self, format, *args):
        # Don't bother logging requests for admin images or the favicon.
        if (self.path.startswith(self.admin_static_prefix)
                or self.path == '/favicon.ico'):
            return

        msg = "[%s] %s\n" % (self.log_date_time_string(), format % args)

        # Utilize terminal colors, if available
        if args[1][0] == '2':
            # Put 2XX first, since it should be the common case
            msg = self.style.HTTP_SUCCESS(msg)
        elif args[1][0] == '1':
            msg = self.style.HTTP_INFO(msg)
        elif args[1] == '304':
            msg = self.style.HTTP_NOT_MODIFIED(msg)
        elif args[1][0] == '3':
            msg = self.style.HTTP_REDIRECT(msg)
        elif args[1] == '404':
            msg = self.style.HTTP_NOT_FOUND(msg)
        elif args[1][0] == '4':
            msg = self.style.HTTP_BAD_REQUEST(msg)
        else:
            # Any 5XX, or any other response
            msg = self.style.HTTP_SERVER_ERROR(msg)

        sys.stderr.write(msg)


"""
    django web的执行入口。
    入参： wsgi_handler----wsgi的执行handler，WSGIHandler的对象实例，具备call特性；
"""
def run(addr, port, wsgi_handler, ipv6=False, threading=False):
    server_address = (addr, port)           # 设置服务器的地址和端口号
    if threading:
        """
            type()函数两种使用方式：
                这里得到一个自定义的WSGIServer类，继承至socketserver.ThreadingMixIn和WSGIServer类
        """
        httpd_cls = type(str('WSGIServer'), (socketserver.ThreadingMixIn, WSGIServer), {})
    else:
        httpd_cls = WSGIServer

    """创建一个http server，其中为实例命名为：httpd"""
    httpd = httpd_cls(server_address, WSGIRequestHandler, ipv6=ipv6)
    httpd.set_app(wsgi_handler)         # wsgi_handler是HTTP请求的句柄，set application
    httpd.serve_forever()

"""
    type()函数有两种使用方式：
        type(object)：得到object的类型；
        type(name, bases, dict)：得到一个新的类型
            name----类的名字；
            bases----元组类型作为父类；
            dict----类型的属性参数；
"""

"""
    SocketServer为python中的网络服务框架。这里单独好好介绍一下：
    一、SocketServer简化了网络服务器的编程
        有四个类：TCPServer、UDPServer、UnixStreamServer、UnixDatagramServer。这4个类是同步处理的，所以通过ThreadingMixIn和
        ForkingMixIn类来支持异步。
    二、创建服务器的步骤
        1、创建一个请求类：
            请求类是BaseRequestHandler的子类并重载其handle()方法。
        2、实例化一个服务器类，传入服务器地址和请求处理程序类：
        3、调用handle_request()或者server_forever()
            一般是调用其他事件循环或者使用select()
    三、服务器类型
        5种类型：BaseServer、TCPServer、UnixStreamServer、UDPServer、UNixDatagramServer。其中BaseServer不直接对外提供服务。
    四、服务器对象
        SocketServer.BaseServer：模块中所有服务器对象的超类。定义了接口，大多数的方法不实现，在子类中进行细化。
        相关接口：
            1、BaseServer.fileno()：返回服务器监听套接字的文件描述符。通常传递给select.select()，允许一个进程监听多个服务器。
            2、BaseServer.handle_request()：处理单个请求；
            3、BaseServer.serve_forever(poll_interval=0.5)：处理请求，直到一个明确的shutdown()请求。每poll_interval秒轮询
               一次shutdown。
            4、......

    一般，SocketServer中请求的处理都是同步的。但是同步对于server中的处理，又是不太适合的。
    所以，提供两种服务模型：
    具体选择何种模型，可以考虑是否需要处理请求间数据共享。
    1、SocketServer.ThreadingMixIn
        修改服务器的混合类，可以使用线程服务多个客户端；
        对于创建的线程数量没有任何限制。默认情况下，线程是非守护程序类的，除非将类变量daemon_threads设置为True。
        有新的请求时，创建一个新的线程，在该线程中处理请求。

    2、SocketServer.ForkingMixIn
        有新的请求时，创建一个新的进程，在该进程中处理请求。

    BaseServer提供了几个值得关注的方法：
        server_forever
        _handle_request_noblock
        process_request
        shutdown_request
        shutdown
"""